﻿using System;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using UnityEngine;

namespace winS.Unity
{
    public class Coroutine
    {
        /// <summary>
        /// 协程运行被中断事件
        /// </summary>
        public event Action onStopped;
        /// <summary>
        /// 协程完整运行结束事件
        /// </summary>
        public event Action onCompleted;
        /// <summary>
        /// 协程结束事件.
        /// 即使协程被Stop, 也会触发该事件
        /// </summary>
        public event Action onFinished;

        /// <summary>
        /// 协程是否正在运行
        /// </summary>
        public bool running { get; private set; }

        /// <summary>
        /// 获取这个协程的Task
        /// </summary>
        public Task task => GetTask();

        internal string name => coroutineCore.name;

        /// <summary>
        /// 如果为true, 则当父级协程Stop时, 当前协程继续运行;
        /// 否则, 当前协程会被Stop;
        /// </summary>
        internal bool unaffectedByParentStop;

        internal Action continuation;

        /// <summary>
        /// 内部协程引用
        /// </summary>
        internal CoroutineCore coroutineCore;

        private TaskCompletionSource<bool> taskCompletionSource;

        public static Coroutine Wait(float duration) => CoroutineStarter.Start(WaitProcess(duration));
        public static Coroutine WaitAll(params object[] objects) => CoroutineStarter.Start(WaitAllProcess(objects));
        public static Coroutine WaitFrame() => CoroutineStarter.Start(WaitFrameProcess);
        public static Coroutine WaitRealtime(float duration) => CoroutineStarter.Start(WaitRealtimeProcess(duration));
        public static Coroutine WaitUntilTrue(Func<bool> function) => CoroutineStarter.Start(WaitUntilTrueProcess(function));
        public static Coroutine WaitAsyncOperation(AsyncOperation asyncOperation) => CoroutineStarter.Start(WaitAsyncOperationProcess(asyncOperation));

        private static IEnumerator WaitAllProcess(object[] objects)
        {
            foreach (var waitObject in objects) yield return waitObject;
        }
        private static IEnumerator WaitFrameProcess()
        {
            yield return null;
        }
        private static IEnumerator WaitProcess(float duration)
        {
            yield return new Wait(duration);
        }
        private static IEnumerator WaitRealtimeProcess(float duration)
        {
            yield return new WaitRealtime(duration);
        }
        private static IEnumerator WaitUntilTrueProcess(Func<bool> function)
        {
            yield return new WaitUntilTrue(function);
        }
        private static IEnumerator WaitAsyncOperationProcess(AsyncOperation asyncOperation)
        {
            yield return asyncOperation;
        }

        internal Coroutine(CoroutineCore coroutineCore)
        {
            running = true;
            this.coroutineCore = coroutineCore;
            coroutineCore.onCompleted += OnCompleted;
        }

        /// <summary>
        /// 停止协程
        /// </summary>
        public void Stop()
        {
            if (running && coroutineCore != null)
            {
                onStopped?.Invoke();
                onStopped = null;
                coroutineCore.RequestStop();
                Finish();
            }
        }

        public Coroutine UnaffectedByParentStop()
        {
            unaffectedByParentStop = true;
            return this;
        }

        public CoroutineAwaiter GetAwaiter()
        {
            return new CoroutineAwaiter(this);
        }

        private void Finish()
        {
            onFinished?.Invoke();
            onFinished = null;
            running = false;
            coroutineCore = null;
            taskCompletionSource?.SetResult(false);
            continuation?.Invoke();
        }
        private void OnCompleted()
        {
            onCompleted?.Invoke();
            onCompleted = null;
            Finish();
        }
        private Task GetTask()
        {
            if (taskCompletionSource == null)
            {
                taskCompletionSource = new TaskCompletionSource<bool>();
                if (!running) taskCompletionSource.SetResult(false);
            }
            return taskCompletionSource.Task;
        }

        public readonly struct CoroutineAwaiter : INotifyCompletion
        {
            public bool IsCompleted => !coroutine.running;

            private readonly Coroutine coroutine;

            internal CoroutineAwaiter(Coroutine coroutine) => this.coroutine = coroutine;

            public void GetResult()
            {
            }

            void INotifyCompletion.OnCompleted(Action continuation) => coroutine.continuation = continuation;
        }
    }
}